<?php

use Illuminate\Console\Command;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Output\OutputInterface;
use Illuminate\Support\Pluralizer;

class AppDomainCommandUtil {
   public static function makePath($file, $mode = 0777) {
        $dir = dirname($file);
        if(is_dir($dir)) return;
        @mkdir($dir, $mode, true);
   }
   public static function deleteFile($file) {
	   	@unlink($file);
   }
	public static function isFile($file) {
		return is_file($file);
	}
	public static function exist($path, $file) {
		$glob = glob($path.DIRECTORY_SEPARATOR.'*'.$file);
		return($glob === false || count($glob)==0) ? false : true;
	}
	public static function statement($command, $sql) {
		try {
			$command->info("executing $sql");
			DB::statement($sql);
		} catch (\Exception $ex) {
			$command->info($ex->getMessage());
		}
	}

	public static function files($dir, $recursive = false) {
		$list = array();
		$dir = realpath(str_replace('/', DIRECTORY_SEPARATOR, $dir ));
		if ($dh = opendir($dir)) {
			while (($file = readdir($dh)) !== false) {
				if ($file=='.' || $file=='..') {
					continue;
				}
				$name = $dir. DIRECTORY_SEPARATOR. $file;
				if (is_dir($name)){
					if ($recursive) {
						$list = array_merge($list, AppDomainCommandUtil::files($name, true));
					} else {
						$list[] = $name;
					}
					continue;
				}
				if (is_file($name)){
					$list[] = $name;
				}
			}
			closedir($dh);
		}
		return $list;
	}

	public static function get_duplicated_by_filename($folder, $pattern, $replace ='') {
		$files = AppDomainCommandUtil::files($folder, false);
		$map = array();
		$duplicated = array();
		foreach ($files as $value) {
		    $base = preg_replace($pattern, $replace, basename($value));
		    if (isset($map[$base])) {
		    	preg_match($pattern, basename($value) , $m1);
		    	preg_match($pattern, basename($map[$base]) , $m2);
		        if (strcmp($m1[0],$m2[0]) > 0) {
		            $duplicated[] = $map[$base];
		            $map[$base] = $value;
		        } else {
		            $duplicated[] = $value;
		        }
		    } else {
		        $map[$base] = $value;
		    }
		}
		return $duplicated;
	}

	public static function parseFields($source) {
		foreach( preg_split('/\r?\n/', $source ) as $line ) {
			if(0 !== strpos( $line, ' ')) {
				continue;
			}
			$comment = null;
			if(false !==($pos = strpos( $line, '--'))) {
				$comment = trim( substr( $line, $pos + 2 ) );
				$line = substr( $line, 0, $pos );
			}
			$line = trim( preg_replace('/ +/', ' ', $line ) );
			if(strlen( $line ) < 1) {
				continue;
			}
			$props = preg_split('/ *; */', $line);
			$column = preg_split('/ +/', trim(array_shift($props)), 3);
			$field = array();
			if($comment != null) {
				$field['comment'] = trim($comment);
			}
			if(in_array($column[0], array('primary', 'index', 'unique'))) {
				$field['type'] = array_shift($column);
				$field['name'] = trim(implode(' ', $column));
				$field['system'] = 1;
				$splited [] = $field;
				continue;
			}
			if(in_array($column[0], array('timestamps', 'softDeletes'))) {
				$field['type'] = $column[0];
				$field['system'] = 2;
				$splited [] = $field;
				continue;
			}
			if(in_array($column[0], array('treemodel'))) {
				foreach (array('node_parent', 'node_left', 'node_right', 'node_depth', 'node_flag') as $key) {
					$field['type'] = 'integer';
					$field['name'] = $key;
					$field['validator'] = array('integer');
					$field['nullable'] = in_array($key, array('node_left', 'node_right')) ? null : 1;
					$splited [] = $field;
					if ($key == 'node_flag') {
						$field['comment'] = trim('ノード操作BITフラグ(0b0001:削除不可,0b0010:編集不可)');
					}
				}
				$field = array();
				foreach (array('node_parent', 'node_left', 'node_right', 'node_depth') as $key) {
					$field['type'] = 'index';
					$field['name'] = $key;
					$field['system'] = 1;
					$splited [] = $field;
				}
				continue;
			}

			$field['name'] = $column[0];
			$field['type'] = $column[1];
			if (count($column) > 2) {
				$field['size'] = $column[2];
			}
			foreach ($props as $key) {
				if ($key == '') continue;
				if(in_array( $key, array('nullable','unsigned','unique') )) {
					$field[$key] = 1;
				} else {
					if(false !==($pos = strpos( $key, 'default '))) {
						$field['default'] = trim(substr($key, 8));
					} else {
						dd( $line );
					}
				}
			}

			//validator
			$rule = array();
			if(!isset($field['nullable'])) $rule[] = 'required';
			if(isset($field['type'])) {
				if(in_array($field['type'], array('integer', 'bigInteger', 'smallInteger'))) $rule[] = 'integer';
				if(in_array($field['type'], array('float', 'decimal'))) $rule[] = 'numeric';
				if(in_array($field['type'], array('boolean'))) $rule[] = 'boolean';
			}
			if(isset($field['size'])) $rule[] = 'max:'.$field['size'];
			if(count($rule) > 0) $field['validator'] = $rule;

			$splited[] = $field;
		}
		return $splited;
	}
}

class AppDomainCommand extends Command {
	protected $name = 'app:domain';
	protected $description = 'Command description.';
	protected $replaces = array();
	protected $fields = array();
	protected $pks = array();
	protected $modeltype ='simple';

	public function getTemplatePath() {
		return $this->option('template');
	}

	public function getAppPath() {
		return app('path');
	}
	public function getSourcePath($mvc) {
		if($this->option('path') != null) {
			return $this->option('path').'/'.$this->normalizePath($this->option('namespace')).'/'.$this->argument('domain').'/'.$mvc;
		}
		return $this->getAppPath().'/'.$mvc.'/'.$this->normalizePath($this->option('namespace'));
	}
	public function getTestPath($mvc) {
		if($this->option('path') != null) {
			return $this->option('path').'/../tests/'.$this->normalizePath($this->option('namespace')).'/'.$this->argument('domain').'/'.$mvc;
		}
		return $this->getAppPath().'/'.$mvc.'/'.$this->normalizePath($this->option('namespace'));
	}

	public function getViewPath($view) {
		if($this->option('path') != null) {
			$model = strtolower(str_replace("_", "/", $this->argument('entity')));
			return $this->option('path').'/views/'.$this->normalizePath($this->option('namespace')).'/'.$this->argument('domain')."/$model/$view";
		}
		return $this->getAppPath().'/views/'.$this->normalizePath($this->option('namespace'));
	}

	public function getDatabasePath() {
		if($this->option('path') != null) {
			return $this->option('path').'/nimo/database';
		}
		return $this->getAppPath().'/database';
	}

	public function fire() {
		$this->init();

		$this->generateModel();
		$this->generateController();
		$this->generateView();
		$this->generateMigration();
		$this->generateSeed();
		$this->generateControllerTest();
		$this->updateConfigs();
		$this->clearMigrations();
	}

	protected function generateModel() {
		$template = $this->getTemplatePath().'/model.txt';
		$php = $this->getSourcePath('models').'/'.$this->getModelClass().'.php';
		$this->generateFile( $template, $php, $this->replaces );
	}

	protected function generateController() {
		$template = $this->getTemplatePath().($this->modeltype =='node' ? '/controller-node.txt': '/controller.txt');
		$php = $this->getSourcePath('controllers').'/'.$this->getModelClass().'Controller.php';
		$this->generateFile( $template, $php, $this->replaces );
	}

	protected function generateControllerTest() {
		$template = $this->getTemplatePath().'/controller-test.txt';
		$php = $this->getTestPath('controllers').'/'.$this->getModelClass().'ControllerTest.php';
		$this->generateFile( $template, $php, $this->replaces );
	}

	protected function generateView() {
		$views = array('index','show','create','edit');
		foreach( $views as $view ) {
			if ($this->modeltype =='node' && in_array($view, array('index'))) {
				$template = $this->getTemplatePath()."/views/$view-node.txt";
							} else {
				$template = $this->getTemplatePath()."/views/$view.txt";
			}
			$php = $this->getViewPath($view).'.blade.php';
			$this->generateFile( $template, $php, $this->replaces );
		}
	}
	protected function generateMigration() {
		$template = $this->getTemplatePath().'/migration.txt';
		$folder = $this->getDatabasePath().'/migrations';
		$migrate = 'create_'.$this->getTablename().'_table.php';
		$php = $folder.'/'.date('Y_m_d_His').'_'.$migrate;
		$folder = str_replace('/', DIRECTORY_SEPARATOR, $folder);
		if ($this->option('forced') != null) {
			foreach (glob($folder.DIRECTORY_SEPARATOR.'*'.$migrate) as $f) {
				//AppDomainCommandUtil::statement($this, 'drop table '.$this->getTablename());
				AppDomainCommandUtil::statement($this, 'delete from migrations where migration like \'%' .$this->getTablename(). '_table%\'');
				$this->info("deleting $f");
	            AppDomainCommandUtil::deleteFile($f);
	        }
		} else {
			if (AppDomainCommandUtil::exist($folder,$migrate)){
				$php = str_replace('/', DIRECTORY_SEPARATOR, $php);
				$this->info("skipping $php");
				return;
			}
		}
		$this->generateFile( $template, $php, $this->replaces );
	}
	protected function generateSeed() {
		$template = $this->getTemplatePath().'/seed.txt';
		// $php = $this->getDatabasePath().'/seeds/'.date('Y_m_d_His').'_'.$this->getTablename().'Seeder.php';
		$php = $this->getDatabasePath().'/seeds/'.$this->replaces ['{{seederClass}}'].'.php';
		$this->generateFile( $template, $php, $this->replaces );
	}

	protected function generateFile($template, $php, $replaces) {
		$php = str_replace('/', DIRECTORY_SEPARATOR, $php);
		if ( $this->option('forced') == null && AppDomainCommandUtil::isFile($php) ){
			$this->info("skipping $php");
			return;
		}
		AppDomainCommandUtil::makePath($php);
		if($this->output->getVerbosity() == OutputInterface::VERBOSITY_DEBUG) {
            $this->info("generating $php using $template");
		}
		if(false ===($source = file_get_contents($template))) {
			$this->info("no template file");
			return;
		}
		foreach( $replaces as $key => $value ) {
			$source = str_replace( $key, $value, $source );
		}
		if($this->output->getVerbosity() == OutputInterface::VERBOSITY_DEBUG) {
			$this->line( $source);
		}
		file_put_contents($php, $source);
		$this->info("generated ".realpath($php));
	}

	protected function updateConfigs() {
		$route = str_replace('.', '/', $this->getRoutefolder());
		$namespace = $this->getBaseNamespace();
		$controller = $this->normalizePath("$namespace.controllers." ).$this->getModelClass().'Controller';
		$route="Route::resource('$route', '$controller');";
		$this->info('-------------------------------------');
		$this->info('add to route >'.$route);
		$name = str_replace('.', '/', $this->getRoutefolder());
		$url = " <li {{ (Request::is('$name') ? 'class=\"active\"' : '') }}><a href=\"{{ URL::to('$name') }}\"><i class=\"icon-file icon-white\"></i>$name</a></li>";
		$this->info('add to view  >' .$url);
		$this->info('-------------------------------------');
		$folder = str_replace('/', DIRECTORY_SEPARATOR, $this->getDatabasePath().'/migrations');
		$this->info('execute > php artisan migrate --path='.$folder);

		$seederClass = str_replace('/', DIRECTORY_SEPARATOR, 'nimo/database/seeds/'.$this->replaces ['{{seederClass}}']);
		$this->info("execute > php artisan db:seed --class=$seederClass");
		$this->info('-------------------------------------');
	}

	protected function init() {
		$this->parsefields();

		$namespace = $this->getBaseNamespace();
		$entity = $this->getModelClass();
		// model
		$this->replaces ['{{namespaceModel}}'] = $this->normalizePath( "$namespace.models" );
		$this->replaces ['{{classModel}}'] = $entity;
		$this->replaces ['{{classModelBase}}'] = $this->getModelBaseClass();
		$this->replaces ['{{modelTable}}'] = $this->getTablename();
		$this->replaces ['{{fieldRules}}'] = $this->getFieldRules();
		$this->replaces ['{{primaryKey}}'] = $this->getPrimaryKey();
		$this->replaces ['{{primaryKeyMethod}}'] = count($this->pks)>1 ? $this->getPrimaryKeyMethod() : '';
		$this->replaces ['{{guarded}}'] = ($this->modeltype =='node' ? '//' : '') . 'protected $guarded = array()';

		// controller
		$this->replaces ['{{controllerNamespace}}'] = $this->normalizePath( "$namespace.controllers" );
		$this->replaces ['{{classController}}'] = $entity.'Controller';
		$this->replaces ['{{modelClass}}'] = $entity;
		$this->replaces ['{{modelInstance}}'] = lcfirst($entity);
		if (count($this->pks)>1) {
			$this->replaces ['{{modelInstanceGetPk}}'] = '$'.lcfirst($entity).'->getJointPk()';
			$this->replaces ['{{jointPk}}'] = 'ByJointPk';
		} else {
			$this->replaces ['{{modelInstanceGetPk}}'] = '$'.lcfirst($entity).'->id';
			$this->replaces ['{{jointPk}}'] = '';
		}

		$this->replaces ['{{collection}}'] = Pluralizer::plural( $this->argument('entity') );
		$this->replaces ['{{viewfolder}}'] = $this->getViewfolder();
		$this->replaces ['{{routefolder}}'] = $this->getRoutefolder();
		// views
		$this->replaces ['{{formCreateElements}}'] = $this->makeFormElements('create', $this->fields );
		$this->replaces ['{{formEditElements}}'] = $this->makeFormElements('edit', $this->fields );
		// And finally create the table rows
		list( $headings, $modelFields ) = $this->makeTableRows($this->fields);
		$this->replaces ['{{modelHeadings}}'] = $headings;
		$this->replaces ['{{modelFields}}'] = $modelFields;
		$this->replaces ['{{modelFieldsVert}}'] = $this->makeTableRowsVert($this->fields);

		// migrate
		$this->replaces ['{{migrateCreate}}'] = ucfirst( \Str::camel('Create_'.$this->getTablename().'_table') );
		$this->replaces ['{{migrateElements}}'] = $this->makeMigrateElements( $this->fields );
		// seed
		$this->replaces ['{{seederClass}}'] = ucfirst( \Str::camel( $this->getTablename().'Seeder') );
		$this->replaces ['{{seederFields}}'] = $this->makeSeederElements($this->fields);
		// test
		$this->replaces ['{{routefolderTest}}'] = str_replace('.', '/', $this->getRoutefolder());

	}

	protected function getBaseNamespace() {
		// nimo\domains\master
		return $this->normalizePath( $this->option('namespace').'.'.$this->argument('domain') );
	}

	protected function getShortWord($str) {
		$lines = preg_split( "/(\.|\/)/", $str );
		$s = '';
		foreach( $lines as $value ) {
			$s = $s.substr( $value, 0, 1 );
		}
		return $s;
	}
	protected function getBaseNamespaceShort() {
		return $this->getShortWord($this->option('namespace')).'_'.$this->argument('domain');
	}

	protected function getModelClass() {
		return ucfirst( \Str::camel( $this->argument('entity') ) );
	}

	protected function getModelBaseClass() {
		if ($this->modeltype =='node') {
			return 'BaseNodeModel';
		}
		return 'BaseModel';
	}

	protected function getViewfolder() {
		$prefix = $this->option('namespace').'.'.$this->argument('domain');
		$model = str_replace("_", ".", $this->argument('entity'));
		return strtolower( "$prefix.$model" );
	}

	protected function getRoutefolder() {
		$prefix = '';
		if ( $this->option('nameprefix') == 'no') {
		} else {
			$prefix = $this->getShortWord($this->option('namespace')).'.';
		}
		$model = str_replace("_", ".", $this->argument('entity'));
		return strtolower($prefix.$this->argument('domain').'.'.$model);
	}

	protected function getTablename() {
		$prefix = '';
		if ( $this->option('nameprefix') == 'no') {
			$prefix = $this->argument('domain');
		} else {
			$prefix = $this->getBaseNamespaceShort();
		}
		return strtolower( $prefix.'_'.$this->getModelClass() );
	}

	protected function getPrimaryKey() {
		if (count($this->pks) <= 1) {
			return '';
		}
		return "\n	protected \$primaryKey = array('".implode('\', \'', $this->pks)."');\n";
	}

	protected function getPrimaryKeyMethod() {
		$source = "\n    public \$incrementing = false;\n";
		//file_get_contents($this->getTemplatePath().'/model-pk.txt');
		return $source;
	}

	protected function getFieldRules() {
		$rules = array();
		foreach( $this->fields as $f ) {
			if (isset($f['validator']))
			$rules[] = '\''.$f['name'].'\' => \''. implode('|', $f['validator']) . '\',';
		}
		return "\n        ".implode("\n        ", $rules);
	}

	protected function normalizePath($str) {
		$str = str_replace('.', DIRECTORY_SEPARATOR, $str );
		return str_replace('/', DIRECTORY_SEPARATOR, $str );
	}

	protected function getArguments() {
		return array(
			array('domain',InputArgument::REQUIRED,'An domain argument.'),
			array('entity',InputArgument::REQUIRED,'An entity argument.')
		);
	}
	protected function getOptions() {
		return array(
			array('namespace',null,InputOption::VALUE_OPTIONAL,'An namespace option.','nimo.domains'),
			array('template',null,InputOption::VALUE_OPTIONAL,'An template path  option.',app_path().'/lib/stubs/domain'),
			array('path',null,InputOption::VALUE_OPTIONAL,'An source out path option.',null ),
			array('fields',null,InputOption::VALUE_OPTIONAL,'Table fields',null ) ,
			array('forced',null,InputOption::VALUE_OPTIONAL,'regenerate all', null ) ,
			array('nameprefix',null,InputOption::VALUE_OPTIONAL,'set namespace prefix  for table, view option.',null),
		);
	}

	public function makeFormElements($action, $fields) {
		$formMethods = array();
		foreach( $fields as $field ) {
			if(isset($field ['system'])) continue;
			$name = $field ['name'];
			$type = $field ['type'];
			$formalName = ucwords( $name );
			$options = array('class' => 'input-block-level','placeholder' => $formalName,'title' => $formalName );
			// TODO: add remaining types
			if('edit' == $action) {
				$value = "{{ Input::old('$name', \$".$this->replaces ['{{modelInstance}}']."->$name) }}";
			} else {
				$value = "{{ Input::old('$name') }}";
			}

			switch($type) {
				case 'integer' :
					// $element = "{{ Form::input('number', '$name') }}";
					$element = Form::input('number', $name, $value, $options );
					break;
				case 'text' :
					// $element = "{{ Form::textarea('$name') }}";
					$element = Form::textarea($name, $value, array_merge($options, array('rows' => 2,'cols' => 100 )));
					break;
				case 'boolean' :
					// $element = "{{ Form::checkbox('$name') }}";
					$element = Form::checkbox( $name, 1, "{{ Input::old('$name') == 1}}" );
					break;
				default :
					// $element = "{{ Form::text('$name') }}";
					$element = Form::text( $name, $value, $options );
					break;
			}

			$element = $this->replace( $element );

			$frag = <<<EOT
<div  class="span3 control-group {{ \$errors->first('$name', ' error') }}">
	<label class="control-label" for="$name">$formalName</label>
	$element
	{{ \$errors->first('$name', '<span class="help-block">:message</span>') }}
</div>
EOT;
			$formMethods [] = $frag;
		}
		return implode( PHP_EOL, $formMethods );
	}

	function replace($str) {
		$str = str_replace('&#039;', "'", $str );
		$str = str_replace('&gt;', ">", $str );
		return $str;
	}

	protected function makeTableRows($fields) {
		$model = "";
		$headings = array();
		$rows = array();
		$model = $this->replaces ['{{modelInstance}}'];
		foreach( $fields as $f) {
			if(isset($f['system'])) continue;
			$headings [] = '<th>'.ucwords( $f ['name'] ).'</th>';
			$rows [] = '<td>{{ $'.$model.'->'.$f ['name'].' }}</td>';
		}
		$headings = implode( "\n", $headings );
		$rows = implode( "\n", $rows );
		return array($headings,$rows );
	}

	protected function makeTableRowsVert($fields) {
		$model = "";
		$rows = array();
		$model = $this->replaces ['{{modelInstance}}'];
		foreach( $fields as $f ) {
			if(isset($f['system'])) continue;
			$rows [] = '<tr> <th>'.ucwords( $f ['name'] ).'</th> <td>{{ $'.$model.'->'.$f ['name'].' }}</td> </tr> ';
		}
		$rows = implode( "\n", $rows );
		return $rows;
	}

	public function makeMigrateElements($fields) {
		$formMethods = array();
		//dd($this->fields);
		foreach( $this->fields as $f ) {
			$s = '            $table->';
			if(isset( $f ['type'] )) {
				$s = $s.$f ['type'].'(';
				if(in_array($f['type'], array('primary', 'index', 'unique'))) {
					$s = $s.' array(\''.str_replace(' ', '\', \'', trim( $f ['name'] ) ).'\')';
				} else if (isset($f['name'])) {
					$s = $s.'\''.$f ['name'].'\'';
				}
				if($f ['type'] == 'enum') {
					$s = $s.', array(\''.str_replace(' ', '\', \'', trim( $f ['size'] ) ).'\')';
				} else {
					if(isset( $f ['size'] )) {
						$s = $s.', '.str_replace(' ', ', ', trim( $f ['size'] ) );
					}
				}
				$s = $s.')';
			} else {
				$s = $s.$f ['name'].'()';
			}
			if(isset( $f ['unique'] )) {
				$s = $s.'->unique()';
			}
			if(isset( $f ['nullable'] )) {
				$s = $s.'->nullable()';
			}
			if(isset( $f ['default'] )) {
				if(in_array( $f ['type'], array('string','text') )) {
					$s = $s.'->default(\''.$f ['default'].'\')';
				} else {
					$s = $s.'->default('.$f ['default'].')';
				}
			}
			$s = $s.';';
			if (isset( $f ['comment'] )) {
				$s = sprintf("%-70s  // %s", $s, $f ['comment']);
			}
			$formMethods [] = $s;
		}
		return implode( PHP_EOL, $formMethods );
	}
	public function makeSeederElements($fields) {
		$elements = array();
		//dd($this->fields);
		foreach( $this->fields as $f ) {
			if(!isset( $f ['name'] )) {
				continue;
			};
			$name = $f ['name'];
			$value = 'null';
			if (isset($f['default'])) {
				$value = "'".$f['default']."'";
			} else if (!isset($f['nullable'])) {
				$value = "'"."'";
			}
			$s = "'$name' => $value";
			$elements [] = $s;
		}
		$s = '            array('.implode( ', ', $elements).') ,';
		return $s;
	}

	public function parsefields() {
		$fields = $this->option('fields');
		if(is_null( $fields ))
			return;
		if(file_exists($path = str_replace('/', DIRECTORY_SEPARATOR, $fields))) {
			$source = file_get_contents($path);
		} else {
			$source = str_replace(',', "\n    ", '    '.$fields);
			$source = str_replace(':', ' ', $source);
		}
		$source = str_replace("\t", '    ', $source);
		$this->fields = AppDomainCommandUtil::parseFields($source);

		foreach( $this->fields as $f ) {
			if (isset($f['type']) && $f['type']=='primary') {
				$this->pks = preg_split('/ +/', trim($f ['name']));
			}
			if (isset($f['name']) && $f['name']=='node_parent') {
				$this->modeltype ='node';
			}
		}
	}

	public function clearMigrations() {
		$migration = str_replace('/', DIRECTORY_SEPARATOR, $this->getDatabasePath().'/migrations');
		$suffix_pattern = '/^\\d+_\\d+_\\d+_\\d+_/';
		$files = AppDomainCommandUtil::get_duplicated_by_filename($migration, $suffix_pattern);
		if (count($files) > 0) {
			$this->info("--- deleting old migrations files ---");
			foreach ($files as $file) {
				$this->info("$file");
			    AppDomainCommandUtil::deleteFile($file);
			}
		}
	}
}
